<?php

namespace System\library\pattern;

/**
 * <b>Class</b> :        Singleton<br>
 * <b>Fonction</b> :     Permet de construction singleton via l'extension de cette classe
 * <br>
 * @version              1.0
 * @author               Dread <dreadlokeur@gmail.com>
 * @license              Licence publique générale GNU http://www.gnu.org/licenses/gpl.html
 * @copyright            Copyright 2011 - MidichloriansPHP and contributors
 * @package              System
 * @subpackage           library\pattern
 */
abstract class Singleton {

    /**
     * Stock les instances des classes singleton
     *
     * @access private
     * @static
     * @var array
     */
    private static $_singletons = array();
    /**
     * Permet de définir si les singletons peuvent être multiple
     *
     * @access private
     * @static
     * @var bool
     */
    private static $_multiSingleton = false;
    /**
     * Permet de verouiller un singleton pour éviter le loop infini
     *
     * @access private
     * @static
     * @var array
     */
    private static $_lock = array();

    /**
     * Permet de définir si les singletons peuvent être multiple
     *
     * @access public
     * @static
     * @param bool $bool Singleton multiple ou non
     * @return void
     */
    public static function setMultiSingleton($bool) {
        if (!is_bool($bool))
            throw new \Exception('multiSingleton parametre doit être un boolean');
        self::$_multiSingleton = $bool;
    }

    /**
     * Permet d'obtenir l'instance d'un singleton, si celle-ci n'existe pas, elle sera crée
     * Important: si plusieurs paramètres sont passer sous forme d'array, mettre le constructeur de la class en public
     * (plus d'info see: self::_registerSingleton();
     * @access public
     * @static
     * @param mixed $parameters Parametre à passer au constructeur de la class singleton, si plusieurs utiliser un array
     * @param int $instanceNumber Si un singleton est multi, obtenir l'un de ses singletons
     * @return array return l'instance de classe stocké dans l'array singletons
     */
    public static function getInstance($parameters = array(), $singletonNumber = null) {
        $singleton = get_called_class();
        if (!isset(self::$_singletons[$singleton]) || self::$_multiSingleton)
            self::_registerSingleton($singleton, $parameters);

        return self::_getSingleton($singleton, $singletonNumber);
    }

    /**
     * Permet d'obtenir un array contenant tous les singletons
     *
     * @access public
     * @static
     * @param void
     * @return array l'array contenant tous les singletons
     */
    public static function getAllSingletons() {
        return self::$_singletons;
    }

    /**
     * Permet d'obtenir un array contenant toutes les singleton d'un singleton
     *
     * @access public
     * @static
     * @param string $singleton le nom du singleton
     * @return array l'array contenant tous les singletons du singleton
     */
    public static function getSingleton($singleton) {
        if (!isset(self::$_singletons[$singleton]))
            throw new \Exception('Singleton "' . $singleton . '" n\'existe pas');
        return self::$_singletons[$singleton];
    }

    /**
     * Permet de compter le nombre de singletons
     *
     * @access public
     * @static
     * @param void
     * @return int le nombre de singletons
     */
    public static function countAllSingletons() {
        return count(self::$_singletons);
    }

    /**
     * Permet de compter le nombre de singletons d'un singleton
     *
     * @access public
     * @static
     * @param string le nom du singleton
     * @return int le nombre de singletons du singleton
     */
    public static function countAllSingletonsIntoSingleton($singleton) {
        if (!isset(self::$_singletons[$singleton]))
            throw new \Exception('Singleton "' . $singleton . '" n\'existe pas');
        return count(self::$_singletons[$singleton]);
    }

    /**
     * Permet de détruire tous les singletons enregistrés
     *
     * @access public
     * @static
     * @param void
     * @return void
     */
    public static function destructAllSingletons() {
        self::$_singletons = array();
    }

    /**
     * Permet de détruire un singletons dans la liste des singletons enregistrés
     *
     * @access public
     * @static
     * @param string le nom du singleton
     * @return void
     */
    public static function destructSingleton($singleton) {
        if (!isset(self::$_singletons[$singleton]))
            throw new \Exception('Singleton "' . $singleton . '" n\'existe pas');
        unset(self::$_singletons[$singleton]);
    }

    /**
     * Si un singleton est multi, permet de détruire l'un de ses singletons
     *
     * @access public
     * @static
     * @param string le nom du singleton multi
     * @param int l'un des singletons du singleton
     * @return void
     */
    public static function destructSingletonIntoSingleton($singleton, $singletonNumber) {
        if (!isset(self::$_singletons[$singleton]))
            throw new \Exception('Singleton "' . $singleton . '" n\'existe pas');
        if (!isset(self::$_singletons[$singleton][$singletonNumber]))
            throw new \Exception('Singleton number "' . $singletonNumber . '" n\'existe pas');

        unset(self::$_singletons[$singleton][$singletonNumber]);
    }

    /**
     * Permet d'enregistrer un singleton dans la liste des singletons
     *
     * @access private
     * @static
     * @param string le nom du singleton
     * @param array les paramètrer à passer au constructeur du singleton sous forme d'array
     * @return void
     */
    private static function _registerSingleton($singleton, $parameters) {
        if (self::_isLocked($singleton))
            throw new \Exception('Loop singleton');

        self::_lockSingleton($singleton);
        $num = (!self::$_multiSingleton) ? 0 : '';

        if (empty($parameters))
            self::$_singletons[$singleton][$num] = new $singleton();
        // Un seul parametre
        elseif (!is_array($parameters))
            self::$_singletons[$singleton][$num] = new $singleton($parameters);
        // plusieurs parametres, sous forme d'array, le constructeur doit être public !
        // TODO meilleur methode, pour éviter d'avoir un constucteur public pour un singleton
        else {
            $ref = new \ReflectionClass($singleton);
            self::$_singletons[$singleton][$num] = $ref->newInstanceArgs($parameters);
        }

        self::_unLockSingleton($singleton);
    }

    /**
     * Permet de verifier si un singleton est verouillé
     *
     * @access private
     * @static
     * @param string le nom du singleton
     * @return bool est verouillé ou non
     */
    private static function _isLocked($singleton) {
        return (bool) (isset(self::$_lock[$singleton]));
    }

    /**
     * Permet de verouiller un singleton
     *
     * @access private
     * @static
     * @param string le nom du singleton
     * @return void
     */
    private static function _lockSingleton($singleton) {
        self::$_lock[$singleton] = true;
    }

    /**
     * Permet de déverouiller un singleton
     *
     * @access private
     * @static
     * @param string le nom du singleton
     * @return void
     */
    private static function _unLockSingleton($singleton) {
        if (!self::_isLocked($singleton))
            throw new \Exception('Singleton "' . $singleton . '" ne peut être déverouillé car il n\'est pas verouillé');
        unset(self::$_lock[$singleton]);
    }

    /**
     * Permet d'obtenir un singleton
     *
     * @access private
     * @static
     * @param string le nom du singleton
     * @return object le singleton
     */
    private static function _getSingleton($singleton, $singletonNumber) {
        if ($singletonNumber !== null) {
            if (!is_int($singletonNumber))
                throw new \Exception('Singleton number parametre doit être de type interger');
            if (!isset(self::$_singletons[$singleton][$singletonNumber]))
                throw new \Exception('Singleton number "' . $singletonNumber . '"n\'existe pas');

            return self::$_singletons[$singleton][$singletonNumber];
        }
        return self::$_singletons[$singleton][0];
    }

    /**
     * Methode magic Clone, permet d'interdir le clonage
     *
     * @access protected
     * @param void
     * @return void
     */
    protected function __clone() {
        if (!self::$_multiSingleton)
            throw new \Exception('Le clonage d\'un singleton est interdit');
    }

}

?>
